# -*- coding: utf-8 -*-
# Generación de código Python a partir de diseño de módulos.

## vipera, un diseñador de aplicaciones para Python
##     Copyright (C) 2011 Ángel Luis García García
##
##    This program is free software: you can redistribute it and/or modify
##    it under the terms of the GNU General Public License as published by
##    the Free Software Foundation, either version 3 of the License, or
##    (at your option) any later version.
##
##    This program is distributed in the hope that it will be useful,
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##    GNU General Public License for more details.
##
##    You should have received a copy of the GNU General Public License
##    along with this program.  If not, see <http://www.gnu.org/licenses/>.

import wx
import wx.lib.agw.pybusyinfo as PBI

import os
import StringIO
import textwrap

from traductor import t_documentacion
from traductor import t_imports
from traductor import t_estructura
from traductor import t_constantes
from traductor import t_funcion
from traductor import t_clase
from traductor import t_metodo
from traductor import t_codigo_principal
from traductor import f_quitar_lineas_vacias
from configuracion_vipera import proy_por_defecto as por_defecto
from configuracion_vipera import enter
from i18n_vipera import t

class codigo_modulo(object):
    '''Generación de código Python de un módulo en edición'''
    def __init__(self, panel_principal, crear_proyecto = False, 
                 nombre_modulo = None, idioma = None):
        '''Constructor de clase'''
        # Obtenemos número de hoja (módulo) seleccionada.
        # nombre_modulo = panel_principal.m_choice_modulos.GetStringSelection()
        # Características del proyecto.
        carac = panel_principal.panel_conf_vipera
        # Buscamos codificación (en las características del proyecto).
        aux = carac.m_textCtrl_codif.GetValue()
        if len(aux) !=0:
            codif = '# -*- coding: %s -*-' % aux
        else:
            codif = None
        # Buscamos indentación (en las características del proyecto).
        indent = carac.m_spinCtrl_indentacion.GetValue()
        if indent is None: indent = 4
        # [03-10-2011] Buscamos hacer el script ejecutable.
        if carac.m_checkBox_script_eje.GetValue():
            script_ejecutable = carac.m_textCtrl_script_eje.GetValue()
        else:
            script_ejecutable = None

        # Panel de código principal.
        codigo_a_ejecutar = self.__panel_codigo_principal(panel_principal, 
                                                          indent, carac, 
                                                          crear_proyecto, nombre_modulo)
        # Panel de documentación.
        documentacion = self.__panel_documentacion(panel_principal, indent,
                                                   nombre_modulo)
        # Panel de imports.
        imports = self.__panel_imports(panel_principal, indent)
        # Panel de registros.
        registros = self.__panel_registros(panel_principal, indent)
        # Panel de constantes.
        constantes = self.__panel_constantes(panel_principal, indent)
        # Panel de funciones.
        funciones = self.__panel_funciones(panel_principal, indent)
        # Panel de clases.
        codigo_clases = self.__panel_clases(panel_principal, indent)
        # Creamos código.
        self.ret = self.__crear_modulo(carac, script_ejecutable, codif, codigo_a_ejecutar,
                                       documentacion, imports, registros, constantes,
                                       funciones, codigo_clases, idioma)
        
    def codigo_python_generado(self):
        '''Devuelve código Python de módulo'''
        return self.ret
    
    def __crear_modulo(self, carac, script_ejecutable, codif, codigo_a_ejecutar,
                     documentacion, imports, registros, constantes,
                     funciones, codigo_clases, idioma):
        '''Crea código de módulo Python'''
        ret = ''
        if script_ejecutable is not None:
            ret += script_ejecutable + enter
        if codif is not None:
            ret += codif + enter
        # Cabecera indentificadora de la aplicación.
        if carac.m_checkBox_cabecera_vipera.Get3StateValue():
            msj1 = t(u'Generado por', idioma)
            msj = u'## %s %s %s (%s)' % (msj1, 
                                         por_defecto['NOMBRE_APLIC'],
                                         por_defecto['VERSION'],
                                         por_defecto['FECHA_CREACION'])
            ret += msj + enter
        # Si hay LICENSE para módulos, se incluye.
        if carac.m_checkBox_aplic_lic_mod.Get3StateValue():
            # Obtenemos texto de licencia.
            texto_licencia = carac.m_textCtrl_LICENSE.GetValue()
            # Lo ajustamos a 70 columnas.
            #lineas = textwrap.wrap(texto_licencia, 70)
            #texto_licencia = ''
            #for i in lineas: texto_licencia += i + enter
            # Incluimos almohadillas para comentar código.
            if len(texto_licencia.strip()) != 0:
                texto_aux = StringIO.StringIO(texto_licencia)
                texto_comentado = ''
                for i in texto_aux: 
                    texto_comentado += u'##  %s' % i
                arriba = abajo = '#' * 75
                texto_comentado = arriba + enter + \
                                texto_comentado + enter + \
                                abajo + enter
                # Incluimos el código.
                ret += texto_comentado
        if documentacion is not None:
            ret += documentacion + enter
        if imports is not None:
            ret += f_quitar_lineas_vacias(imports) + enter
        if constantes is not None:
            ret += f_quitar_lineas_vacias(constantes) + enter
        if registros is not None:
            ret += f_quitar_lineas_vacias(registros) + enter
        if len(funciones.strip()) != 0:
            ret += f_quitar_lineas_vacias(funciones) + enter
        if len(codigo_clases) != 0:
            ret += f_quitar_lineas_vacias(codigo_clases) + enter
      
        # Código a ejecutar.
        if codigo_a_ejecutar is not None:
            ret = u'%s%s%s' % (ret, enter, codigo_a_ejecutar)
        # Devolvemos código.
        return ret
        
    def __panel_codigo_principal(self, panel_principal, indent, 
                                 carac, crear_proyecto, nombre_modulo):
        '''Devuelve código Python del panel principal'''
        codigo_a_ejecutar = None
        panel_m = panel_principal.panel_cod_prin
        ejecutar_codigo = panel_m.m_checkBox_eje.Get3StateValue()
        codigo_script = panel_m.editor.panel.editor.GetText()
        if crear_proyecto:
            # ¿Crear módulo principal?
            if carac.m_checkBox_mod_prin.Get3StateValue():
                # Recogemos nombre del módulo que se requiere ser módulo principal.
                mod_p = carac.m_comboBox_nombre_modulo.GetStringSelection()
                # Si los nombres coinciden se crea código.
                if mod_p == nombre_modulo: 
                    aux = t_codigo_principal(indent, codigo_script)
                    codigo_a_ejecutar = aux.codigo()
        else:
            if ejecutar_codigo:
                # Se crea código.
                aux = t_codigo_principal(indent, codigo_script)
                codigo_a_ejecutar = aux.codigo()
        # Nos vamos.
        return codigo_a_ejecutar
    
    def __panel_documentacion(self, panel_principal, indent, nombre_modulo):
        '''Devuelve código Python de panel de documentación'''
        panel_d = panel_principal.panel_doc.panel
        # [03-10-2011] Si está activado el flag se genera la documentación.
        if panel_d.m_checkBox_activar.GetValue():
            # Recogemos datos.
            nombre = panel_d.m_nombre.GetValue()
            proposito = panel_d.m_proposito.GetValue()
            autor = panel_d.m_autor.GetValue()
            creado = unicode(panel_d.m_datePicker_creado.GetValue().Format('%d-%m-%Y'))
            CopyRight = panel_d.m_copyright.GetValue()
            licencia = panel_d.m_licencia.GetValue()
            # Si no hay nombre, por defecto se incluye el del módulo.
            if len(nombre.strip()) == 0: nombre = u'%s.py' % nombre_modulo
            # Creamos documentación.
            aux = t_documentacion(modulo = nombre, proposito = proposito,
                                  licencia = licencia, creado = creado,
                                  autor = autor, copyRight = CopyRight)
            documentacion = aux.cabecera()
            # Borramos el objeto.
            del aux
        else:
            documentacion = None
        # Nos vamos.
        return documentacion

    def __panel_imports(self, panel_principal, indent):
        '''Devuelve código Python de panel de imports'''
        panel_i = panel_principal.panel_imports
        # Recogemos listas.
        Import_From = panel_i.m_listCtrl_from
        Import = panel_i.m_listCtrl_import
        # Tratamos cada lista.
        lista_imp = list()
        n_imports = Import.GetItemCount()
        for i in range(n_imports):
            j = Import.GetItem(i).GetText()
            lista_imp.append(j)
        lista_imp_f = list()
        n_imports = Import_From.GetItemCount()
        for i in range(n_imports):
            j = Import_From.GetItem(i).GetText()
            lista_imp_f.append(j)
        # Creamos imports.
        if len(lista_imp) == 0: lista_imp = None
        if len(lista_imp_f) == 0: lista_imp_f = None
        aux = t_imports(lista_imp, lista_imp_f)
        imports = aux.struct_import()
        # Borramos el objeto.
        del aux
        # Nos vamos.
        return imports
    
    def __panel_registros(self, panel_principal, indent):
        '''Devuelve código Python de panel de registros'''
        panel_e = panel_principal.panel_registros.m_auinotebook
        nhojas = panel_e.GetPageCount()
        lista_registros = list()
        for i in range(nhojas):
            # Nombre del registro.
            registro = panel_e.GetPageText(i)
            # Hoja actual.
            hoja_bis = panel_e.GetPage(i)
            # Emulamos con clase ó función.
            emular = hoja_bis.m_choice_clase_o_funcion.GetStringSelection()
            # Recogemos los campos del registro.
            lista_campos = list()
            n_registros = hoja_bis.m_listCtrl_estruc.GetItemCount()
            for i in range(n_registros):
                j = hoja_bis.m_listCtrl_estruc.GetItem(i).GetText()
                aux = j.split("=")
                lista_campos.append([aux[0].strip(), aux[1].strip()])
            lista_registros.append((registro, emular, lista_campos))
        # Creamos estructuras.
        aux = t_estructura(indent, lista_registros)
        registros = aux.estructuras()
        # Borramos el objeto.
        del aux
        # Nos vamos.
        return registros
    
    def __panel_constantes(self, panel_principal, indent):
        '''Devuelve código Python de panel de constantes'''
        panel_o = panel_principal.panel_ctes
        # Tratamos constantes.
        lista_cte = list()
        n_cte = panel_o.m_lista_cte.GetItemCount()
        for i in range(n_cte):
            j = panel_o.m_lista_cte.GetItem(i).GetText()
            lista_cte.append(j)
        # Tratamos diccionarios de constantes.
        panel_o = panel_principal.panel_ctes.m_aui_nb_dicc_cte
        nhojas = panel_o.GetPageCount()
        lista_cte_dicc = list()
        for i in range(nhojas):
            # Nombre del diccionario.
            diccionario = panel_o.GetPageText(i)
            # Hoja actual.
            hoja_bis = panel_o.GetPage(i)
            # Recogemos los campos del diccionario.
            lista_campos = list()
            n_dicc = hoja_bis.m_lista_cte.GetItemCount()
            for i in range(n_dicc):
                j = hoja_bis.m_lista_cte.GetItem(i).GetText()
                lista_campos.append(j)
            lista_cte_dicc.append((diccionario, lista_campos))
        # Creamos estructuras.
        aux = t_constantes(0, lista_cte, lista_cte_dicc)
        constantes = aux.estruct_cte()
        # Borramos el objeto.
        del aux
        # Nos vamos.
        return constantes
    
    def __panel_funciones(self, panel_principal, indent):
        '''Devuelve código Python de panel de funciones'''
        panel_f = panel_principal.panel_param_func.modelo
        #modelo = [[funcion, comentario, 
        #            pre, post, codigo, 
        #            [(param, por_defecto, tipo_dato)...]]
        #          ...]
        # Lista de funciones.
        lista_funciones = list()
        # Recorremos el modelo de funciones.
        for func in panel_f:
            # Nombre de la función.
            nombre = func[0]
            # Y demás datos.
            comentario = func[1]
            pre = func[2]
            post = func[3]
            lista_param = list()
            for j in func[5]:
                nombre_param = j[0]
                valor_por_def = j[1]
                tipo_dato = j[2]
                # Si existe el parámetro, se incluye.
                if len(unicode(nombre_param).strip()) != 0:
                    if len(unicode(valor_por_def).strip()) == 0: valor_por_def = None
                    lista_param.append((nombre_param, valor_por_def, tipo_dato))
            # Y recogemos datos.
            codigo = unicode(func[4])
            # ¿Indentación original?
            # Formamos el registro de función.
            if len(unicode(comentario).strip()) == 0: comentario = None
            if len(unicode(pre).strip()) == 0: pre = None
            if len(unicode(post).strip()) == 0: post = None
            if len(lista_param) == 0: lista_param = None
            if len(unicode(codigo).strip()) == 0: codigo = None
            registro = (indent, nombre, 65, comentario, pre, post, lista_param, codigo)
            # Lo incluimos en la lista de funciones.
            lista_funciones.append(registro)
        # Para cada función creamos el código Python correspondiente.
        funciones = ''
        for i in lista_funciones:
            aux = t_funcion(i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[7])
            funciones += u'%s%s' % (aux.funcion(), enter * 2)
            del aux
        # Nos vamos.
        return funciones

    def __panel_clases(self, panel_principal, indent):
        '''Devuelve código Python de panel de clases'''
        panel_c = panel_principal.panel_ogl 
        # Modelo de datos de clases.
        modelo = panel_c.modelo
        # Modelo de relaciones entre clases.
        relacion = panel_c.relacion
        # Modelo de relaciones de dependencia entre clases.
        relacion_dep = panel_c.relacion_dep
        # Obtenemos todas las clases del módulo.
        clases = modelo.clases()
        # Recorremos todas las clases, obteniendo 
        # la información de cada clase.
        codigo_clases = ''
        for i in clases:
            # Posición del dividedshape.
            pos_x = i[1].GetX()
            pos_y = i[1].GetY()
            # Datos de clase.
            nombre = unicode(i[0].nombre).strip()
            clases_externas = i[0].clases_externas
            # codigo_init = i[0].codigo_init
            comentario = i[0].comentario
            # [03-10-2011] Constructor, destructor, constantes de clase.
            codigo_constructor_ini = i[0].codigo_constructor_ini[0]
            flag_codigo_constructor_ini = i[0].codigo_constructor_ini[1]
            if not flag_codigo_constructor_ini or \
               len(codigo_constructor_ini.strip()) == 0:
                codigo_constructor_ini = None
            codigo_constructor_fin = i[0].codigo_constructor_fin[0]
            flag_codigo_constructor_fin = i[0].codigo_constructor_fin[1]
            if not flag_codigo_constructor_fin or \
               len(codigo_constructor_fin.strip()) == 0:
                codigo_constructor_fin = None
            # print i[0].codigo_destructor
            codigo_destructor = i[0].codigo_destructor[0]
            flag_codigo_destructor = i[0].codigo_destructor[1]
            if not flag_codigo_destructor or \
               len(codigo_destructor.strip()) == 0:
                codigo_destructor = None
            constantes_clase = i[0].constantes
            if len(constantes_clase) == 0: constantes_clase = None
            # Parámetros.
            parametros = list()
            for k in i[0].parametros:
                param = k[0]
                valor_ini = k[1]
                parametros.append([param, valor_ini])
            atributos = list()
            for k in i[0].atributos:
                atrib = k[0]
                valor_ini = k[1]
                oculto = k[2]
                atributos.append([atrib, valor_ini, oculto])
            propiedades = list()
            for k in i[0].propiedades:
                prop = k[0]
                valor_ini = k[1]
                info = k[2]
                propiedades.append([prop, valor_ini, info])
            metodos = list(i[0].metodos)
            # Analizamos valores vacíos.
            if len(unicode(clases_externas).strip()) == 0: clases_externas = None
            if len(unicode(comentario).strip()) == 0: comentario = None
            # if len(unicode(codigo_init).strip()) == 0: codigo_init = None
            for m in atributos: 
                if len(unicode(m[1]).strip()) == 0: m[1] = None      
            if len(atributos) == 0: atributos = None
            for m in propiedades: 
                if len(unicode(m[1]).strip()) == 0: m[1] = None      
                if len(unicode(m[2]).strip()) == 0: m[2] = None      
            if len(propiedades) == 0: propiedades = None
            for m in parametros: 
                if len(unicode(m[1]).strip()) == 0: m[1] = None
            if len(parametros) == 0: parametros = None
            if len(metodos) == 0: metodos = None
            # Buscamos las clases de las que hereda la clase actual.
            hereda_de = list()
            for j in relacion.relaciones():
                if j[2] == nombre:
                    # Clase de la que hereda.
                    clase_p = unicode(j[1]).strip()
                    # Buscamos los parámetros de instanciación
                    # de la clase de la que hereda.
                    param2 = None
                    for k in clases:
                        if k[0].nombre == clase_p:
                            param2 = None if len(k[0].parametros) == 0 else k[0].parametros
                            break
                    # Formamos los parámetros de inicialización.
                    aux2 = []
                    if param2 is None:
                        # Incluimos en la lista de clases a heredar.
                        hereda_de.append([clase_p, None])
                    else:
                        for l in param2:
                            if len(unicode(l[1]).strip()) != 0:
                                elem = '%s = %s' % (l[0], l[1])
                            else:
                                elem = l[0]
                            aux2.append(elem)
                        # Incluimos en la lista de clases a heredar.
                        hereda_de.append([clase_p, aux2])
            # Creamos la clase.
            if len(hereda_de) == 0: hereda_de = None
    
            # ################
            # Panel de métodos
            # ################
    
            # [11-08-2011] Recorrido de métodos de clase.
            # [[u'met1', '', [], '', ''], [u'met2', '', [], '', '']]
            metodos_de_clase = ''
            # Recorremos los métodos.
            if metodos is not None:
                for j in metodos:
                    # Recogemos datos.
                    nombre_metodo = unicode(j[0]).strip() 
                    info_metodo = unicode(j[1]).strip() if len(unicode(j[1]).strip()) != 0 else None
                    codigo_metodo = unicode(j[3]).strip() if len(unicode(j[3]).strip()) != 0 else None
                    oculto_metodo = j[4] 
                    # Parámetros: ((param1, valor_ini, tipo_dato), (paramN, valor_ini, tipo_dato))
                    if len(j[2]) == 0: param_metodo = None
                    else:
                        param_metodo = list()
                        for h in j[2]:
                            np = unicode(h[0]).strip()
                            vi = unicode(h[1]).strip() if len(unicode(h[1]).strip()) != 0 else None
                            td = h[2]
                            param_metodo.append((np, vi, td))
                    # Creamos código de método.
                    aux = t_metodo(indent, nombre_metodo, 40, info_metodo, param_metodo, 
                                   codigo_metodo, oculto_metodo)
                    metodos_de_clase += u'%s%s' % (aux.metodo(), enter * 2)
                    del aux
            # Creamos clase.
            aux = t_clase(indent, nombre, hereda_de, 40,
                          comentario, parametros, atributos, 
                          propiedades, clases_externas, metodos,
                          (codigo_constructor_ini, codigo_constructor_fin),
                          codigo_destructor, constantes_clase)
            codigo_clases += u'%s%s%s%s' % (aux.clase(), enter * 2, metodos_de_clase, enter * 2)
            del aux
        # Nos vamos.
        return codigo_clases
